#include "CUtils.h"

#include "precompiled.h"

#include "openssl/applink.c"

#include "openssl/sha.h"
#include "openssl/pem.h"
#include "openssl/rsa.h"

#include "tinyxml/tinyxml.h"

std::string CUtils::sha1(const char* const filename)
{
	FILE* file = fopen(filename, "r");
	if (!file) return "";

	char buffer[4096] = {0};
	unsigned char sha_buffer[20] = {0};
	size_t bytes_read = 0;

	SHA_CTX sha;
	SHA1_Init( &sha );

	while ( bytes_read = fread(buffer, 1, sizeof(buffer), file ) )
	{
		SHA1_Update( &sha, buffer, bytes_read );
	}

	SHA1_Final( sha_buffer, &sha );

	std::string result;
	for (int i = 0; i < 20; i++)
	{
		char buf[3];
		sprintf(buf, "%02x", sha_buffer[i]);
		result.append(buf, 2);
	}

	fclose(file);

	return result;
}

std::string CUtils::encryptPrivateKey(const char* const key_filename, const std::string& buffer)
{
	unsigned char result[4096] = {0};
	unsigned char base64[4096] = {0};

	FILE * priv_key = fopen(key_filename, "r");
	if (!priv_key) return "";
	RSA * rsa = PEM_read_RSAPrivateKey(priv_key, NULL, NULL, NULL);
	if (!rsa) return "";
	size_t result_len = RSA_private_encrypt( buffer.size(), (unsigned char*)buffer.c_str(), result, rsa, RSA_PKCS1_PADDING);
	if (!result_len) return "";
	fclose(priv_key);

	BIO *bmem, *b64;
	BUF_MEM *bptr;

	b64 = BIO_new(BIO_f_base64());
	if ( !b64 ) return "";
	bmem = BIO_new(BIO_s_mem());
	if ( !bmem ) return "";
	b64 = BIO_push(b64, bmem);
	if ( !b64 ) return "";
	if ( !BIO_write(b64, result, result_len) ) return "";
	BIO_flush(b64);
	BIO_get_mem_ptr(b64, &bptr);

	return std::string((char*)bptr->data, bptr->length);
}

std::string CUtils::decryptPublicKey(const char* const key_filename, const std::string& buffer)
{
	unsigned char result[4096] = {0};
	unsigned char buffer_bin[4096] = {0};

	BIO *b64, *bmem;

	b64 = BIO_new(BIO_f_base64());
	if (!b64) return "";
	bmem = BIO_new_mem_buf((void*)buffer.c_str(), buffer.size());
	if (!bmem) return "";
	bmem = BIO_push(b64, bmem);
	if (!bmem) return "";

	size_t buffer_bin_len = BIO_read(bmem, buffer_bin, buffer.size());
	if (!buffer_bin_len) return "";

	BIO_free_all(bmem);

	FILE * pub_key = fopen(key_filename, "r");
	if (!pub_key) return "";
	RSA * rsa = PEM_read_RSA_PUBKEY(pub_key, NULL, NULL, NULL);
	if (!rsa) return "";
	size_t result_len = RSA_public_decrypt( buffer_bin_len, buffer_bin, result, rsa, RSA_PKCS1_PADDING);
	if (!result_len) return "";
	fclose(pub_key);

	return std::string((char*)result, result_len);
}

bool CUtils::sign(const char* const filename, const char* const private_key)
{
	std::string sha = CUtils::sha1(filename); 
	if ( sha.empty() ) return false;

	std::string sign = CUtils::encryptPrivateKey(private_key, sha);
	if ( sign.empty() ) return false;

	std::string sign_filename = std::string(filename) + ".sign.xml";

	FILE* file = fopen(sign_filename.c_str(), "w");
	fwrite("<sign>\n</sign>", 1, strlen("<sign>\n</sign>"), file);
	fclose(file);

	TiXmlDocument doc;
	doc.LoadFile(sign_filename);
	doc.RootElement()->InsertEndChild( TiXmlText( sign ) );
	doc.SaveFile(sign_filename);

	return true;
}

bool CUtils::validateSign(const char* const filename, const char* const public_key)
{
	TiXmlDocument doc;
	if ( !doc.LoadFile(std::string(filename) + ".sign.xml") ) return false;

	std::string sign = doc.RootElement()->GetText();
	if ( sign.empty() ) return false;

	std::string dec_sha = CUtils::decryptPublicKey(public_key, sign);
	if ( dec_sha.empty() ) return false;

	return dec_sha == CUtils::sha1(filename);
}

void CUtils::encodeBase64(const std::string& in, std::string& out)
{
	out.clear();

	BIO *bmem, *b64;
	BUF_MEM *bptr;

	b64 = BIO_new(BIO_f_base64());
	if ( !b64 ) return;
	bmem = BIO_new(BIO_s_mem());
	if ( !bmem ) return;
	b64 = BIO_push(b64, bmem);
	if ( !b64 ) return;
	if ( !BIO_write(b64, in.c_str(), in.size()) ) return;
	BIO_flush(b64);
	BIO_get_mem_ptr(b64, &bptr);

	out.assign( (char*)bptr->data, bptr->length );
}

void CUtils::decodeBase64(const std::string& in, std::string& out)
{
	out.clear();

	BIO *b64, *bmem;

	b64 = BIO_new(BIO_f_base64());
	if (!b64) return;
	bmem = BIO_new_mem_buf((void*)in.c_str(), in.size());
	if (!bmem) return;
	bmem = BIO_push(b64, bmem);
	if (!bmem) return;

	char * ptr = NULL;
	char buffer_bin[8192] = {0};
	if (in.size() >= 8192) ptr = new char[in.size()];
	else ptr = buffer_bin;

	size_t buffer_bin_len = BIO_read(bmem, ptr, in.size());
	if (!buffer_bin_len) return;

	BIO_free_all(bmem);

	out.assign(ptr, buffer_bin_len);

	if (in.size() >= 8192) delete [] ptr;
}

std::string CUtils::str2bin(const char* hex)
{
	std::string bin;
	int len = strlen(hex);
	int step = ((len % 8) == 0) ? 8 : ( ((len % 4) == 0) ? 4 : 2 );
	for (int i = 0; i < len; i += step)
	{
		char buffer[9] = {0};

		if ((len % 8) == 0)
		{
			buffer[6] = hex[i];
			buffer[7] = hex[i+1];
			buffer[4] = hex[i+2];
			buffer[5] = hex[i+3];
			buffer[2] = hex[i+4];
			buffer[3] = hex[i+5];
			buffer[0] = hex[i+6];
			buffer[1] = hex[i+7];
			int b = 0;
			sscanf(buffer, "%x", &b);
			bin.append((char*)&b, 4);
		}
		else if ((len % 4) == 0)
		{
			buffer[2] = hex[i];
			buffer[3] = hex[i+1];
			buffer[0] = hex[i+2];
			buffer[1] = hex[i+3];
			int b = 0;
			sscanf(buffer, "%x", &b);
			bin.append((char*)&b, 2);
		}
		else
		{
			buffer[0] = hex[i];
			buffer[1] = hex[i+1];
			int b = 0;
			sscanf(buffer, "%x", &b);
			bin.append((char*)&b, 1);
		}

	}

	return bin;
}

size_t CUtils::getFileSize(FILE* file)
{
	size_t old = ftell(file);
	fseek(file, 0, SEEK_END);
	size_t size = ftell(file);
	fseek(file, old, SEEK_SET);

	return size;
}